//
//  NVCTouchTask.m
//  EspTouchDemo
//
//  Created by THNVC on 2018/5/30.
//  Copyright © 2018年 Espressif. All rights reserved.
//

#import "NVCTouchTask.h"

#import "ESP_ByteUtil.h"
#import "ESPTouchGenerator.h"
#import "ESPUDPSocketClient.h"
#import "ESPUDPSocketServer.h"
#import "ESP_NetUtil.h"
#import "ESPTouchTaskParameter.h"


#define ONE_DATA_LEN    3

@interface NVCTouchTask () <ESPTouchDelegate>

@property (nonatomic,strong) NSData *apSsid;
@property (nonatomic,strong) NSData *apBssid;
@property (nonatomic,strong) NSData *apPwd;

@property (nonatomic,assign) BOOL isSuc;
@property (nonatomic,assign) BOOL isInterrupt;

@property (nonatomic,strong) ESPUDPSocketClient *client;
@property (nonatomic,strong) ESPUDPSocketServer *server;

@property (nonatomic,strong) NSMutableArray *esptouchResultArray;
@property (nonatomic,strong) NSCondition *condition;

@property (nonatomic,assign) __block BOOL isWakeUp;
@property (nonatomic,assign) volatile BOOL isExecutedAlready;
@property (nonatomic,assign) BOOL isSsidHidden;

@property (nonatomic,strong) ESPTaskParameter *parameter;

@property (nonatomic,strong) NSMutableDictionary *bssidTaskSucCountDict;

@property (nonatomic,strong) NSCondition *esptouchResultArrayCondition;

@property (nonatomic,assign) __block UIBackgroundTaskIdentifier backgroundTask;

@property (nonatomic,strong) id<ESPTouchDelegate> esptouchDelegate;

@property (nonatomic,strong) NSData *localInetAddrData;

@end

@implementation NVCTouchTask

static int datagramCount = 0;

-(instancetype)init {
    if(self = [super init]) {
        self.esptouchDelegate = self;
    }
    return self;
}


-(void)touchTaskWithSsid:(NSString *)ssid AndBSsid:(NSString *)bSsid AndPwd:(NSString *)pwd AndAES:(ESPAES *)aes {
    // ssid  pwd  bssid 转byte
    if(aes == nil){
        self.apSsid = [ESP_ByteUtil getBytesByNSString:ssid];
        self.apPwd = [ESP_ByteUtil getBytesByNSString:pwd];
    }else{
        self.apSsid = [aes AES128EncryptData:[ESP_ByteUtil getBytesByNSString:ssid]];
        self.apPwd = [aes AES128EncryptData:[ESP_ByteUtil getBytesByNSString:pwd]];
    }
    self.apBssid = [ESP_NetUtil parseBssid2bytes:bSsid];
    
    // 获取ip地址
    NSString *localInetAddr4 = [ESP_NetUtil getLocalIPv4];
    if (![ESP_NetUtil isIPv4PrivateAddr:localInetAddr4]) {
        localInetAddr4 = nil;
    }
    NSString *localInetAddr6 = [ESP_NetUtil getLocalIPv6];
    [self.parameter setIsIPv4Supported:localInetAddr4!=nil];
    [self.parameter setIsIPv6Supported:localInetAddr6!=nil];
    
    // 创建UDP client  和  UDP  server
    self.client = [[ESPUDPSocketClient alloc]init];
    self.server = [[ESPUDPSocketServer alloc]initWithPort: [self.parameter getPortListening]
                                          AndSocketTimeout: [self.parameter getWaitUdpTotalMillisecond]];
    
    // 监听ipv6端口
    [self.parameter setListeningPort6:self.server.port];
    
    // ipAddr  和 port  转 data
    if (localInetAddr4!=nil) {
        self.localInetAddrData = [ESP_NetUtil getLocalInetAddress4ByAddr:localInetAddr4];
    } else {
        int localPort = [self.parameter getPortListening];
        self.localInetAddrData = [ESP_NetUtil getLocalInetAddress6ByPort:localPort];
    }
    
    if (DEBUG_ON) {
        NSLog(@"NVCTouchTask --- localInetAddr: %@", [ESP_NetUtil descriptionInetAddr4ByData:self.localInetAddrData]);
    }
    
    // 初始化数据
    [self initTouchTaskData];
}

#pragma mark - 执行操作 expectTaskResultCount:预计的任务结果数
- (NSArray*)executeForResults:(int) expectTaskResultCount
{
    if (expectTaskResultCount <= 0)
    {
        expectTaskResultCount = INT32_MAX;
    }
    [self.parameter setExpectTaskResultCount:expectTaskResultCount];
    
    [self checkTaskValid];
    
    // generator the esptouch byte[][] to be transformed, which will cost
    // some time(maybe a bit much)
    ESPTouchGenerator *generator = [[ESPTouchGenerator alloc]initWithSsid:self.apSsid andApBssid:self.apBssid andApPassword:self.apPwd andInetAddrData:self.localInetAddrData andIsSsidHidden:self.isSsidHidden];
    // listen the esptouch result asyn
    [self listenAsyn:[self.parameter getEsptouchResultTotalLen]];
    BOOL isSuc = NO;
    for (int i = 0; i < [self.parameter getTotalRepeatTime]; i++)
    {
        isSuc = [self execute:generator];  // 执行操作
        if (isSuc)
        {
            return [self getEsptouchResultList];
        }
    }
    
    if (!self.isInterrupt)
    {
        [self sleep: [self.parameter getWaitUdpReceivingMillisecond]];
        [self interrupt];
    }
    
    return [self getEsptouchResultList];
}


- (void)checkTaskValid
{
    if (self.isExecutedAlready){
        perror("ESPTouchTask __checkTaskValid() fail, the task could be executed only once");
    }

    assert(!self.isExecutedAlready);
    self.isExecutedAlready = YES;
}

#pragma mark - 异步监听
- (void) listenAsyn: (const int) expectDataLen
{
    dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self beginBackgroundTask];
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask __listenAsyn() start an asyn listen task, current thread is: %@", [NSThread currentThread]);
        }
        NSTimeInterval startTimestamp = [[NSDate date] timeIntervalSince1970];
        //        NSString *apSsidAndPwd = [NSString stringWithFormat:@"%@%@",self._apSsid,self._apPwd];
        Byte expectOneByte = [self.apSsid length] + [self.apPwd length] + 9;
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask __listenAsyn() expectOneByte: %d",expectOneByte);
        }
        Byte receiveOneByte = -1;
        NSData *receiveData = nil;
        while ([self.esptouchResultArray count] < [self.parameter getExpectTaskResultCount] && !self.isInterrupt)
        {
            if ([self.parameter isIPv4Supported]) {
                receiveData = [self.server receiveSpecLenBytes4:expectDataLen];
            } else {
                receiveData = [self.server receiveSpecLenBytes6:expectDataLen];
            }
            NSLog(@"--------receiveData--------:%@",receiveData);
            if (receiveData != nil)
            {
                [receiveData getBytes:&receiveOneByte length:1];
            }
            else
            {
                receiveOneByte = -1;
            }
            if (receiveOneByte == expectOneByte)
            {
                if (DEBUG_ON)
                {
                    NSLog(@"ESPTouchTask __listenAsyn() receive correct broadcast");
                }
                // change the socket's timeout
                NSTimeInterval consume = [[NSDate date] timeIntervalSince1970] - startTimestamp;
                int timeout = (int)([self.parameter getWaitUdpTotalMillisecond] - consume*1000);
                if (timeout < 0)
                {
                    if (DEBUG_ON)
                    {
                        NSLog(@"ESPTouchTask __listenAsyn() esptouch timeout");
                    }
                    break;
                }
                else
                {
                    if (DEBUG_ON)
                    {
                        NSLog(@"ESPTouchTask __listenAsyn() socketServer's new timeout is %d milliseconds",timeout);
                    }
                    [self.server setSocketTimeout:timeout];
                    if (DEBUG_ON)
                    {
                        NSLog(@"ESPTouchTask __listenAsyn() receive correct broadcast");
                    }
                    if (receiveData != nil)
                    {
                        NSString *bssid =
                        [ESP_ByteUtil parseBssid:(Byte *)[receiveData bytes]
                                          Offset:[self.parameter getEsptouchResultOneLen]
                                           Count:[self.parameter getEsptouchResultMacLen]];
                        NSData *inetAddrData =
                        [ESP_NetUtil parseInetAddrByData:receiveData
                                               andOffset:[self.parameter getEsptouchResultOneLen] + [self.parameter getEsptouchResultMacLen]
                                                andCount:[self.parameter getEsptouchResultIpLen]];
                        [self putEsptouchResultIsSuc:YES AndBssid:bssid AndInetAddr:inetAddrData];
                    }
                }
            }
            else
            {
                if (DEBUG_ON)
                {
                    NSLog(@"ESPTouchTask __listenAsyn() receive rubbish message, just ignore");
                }
            }
        }
        self.isSuc = [self.esptouchResultArray count] >= [self.parameter getExpectTaskResultCount];
        [self interrupt];
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask __listenAsyn() finish");
        }
        [self endBackgroundTask];
    });
}

#pragma mark - BackgroundTask
- (void) beginBackgroundTask
{
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask beginBackgroundTask() entrance");
    }
    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask beginBackgroundTask() endBackgroundTask");
        }
        [self endBackgroundTask];
    }];
}
- (void)endBackgroundTask
{
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask endBackgroundTask() entrance");
    }
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundTask];
    self.backgroundTask = UIBackgroundTaskInvalid;
}

#pragma mark - 执行操作
- (BOOL)execute: (ESPTouchGenerator *)generator
{
    NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
    NSTimeInterval currentTime = startTime;
    NSTimeInterval lastTime = currentTime - [self.parameter getTimeoutTotalCodeMillisecond];
    
    NSArray *gcBytes2 = [generator getGCBytes2];
    NSArray *dcBytes2 = [generator getDCBytes2];
    
    int index = 0;
    
    while (!self.isInterrupt)
    {
        if (currentTime - lastTime >= [self.parameter getTimeoutTotalCodeMillisecond]/1000.0)
        {
            if (DEBUG_ON)
            {
                NSLog(@"ESPTouchTask __execute() send gc code ");
            }
            // send guide code
            while (!self.isInterrupt && [[NSDate date] timeIntervalSince1970] - currentTime < [self.parameter getTimeoutGuideCodeMillisecond]/1000.0)
            {
                // UDP
                [self.client sendDataWithBytesArray2:gcBytes2
                                     ToTargetHostName:[self.parameter getTargetHostname]
                                             WithPort:[self.parameter getTargetPort]
                                          andInterval:[self.parameter getIntervalGuideCodeMillisecond]];
                // check whether the udp is send enough time
                if ([[NSDate date] timeIntervalSince1970] - startTime > [self.parameter getWaitUdpSendingMillisecond]/1000.0)
                {
                    break;
                }
            }
            lastTime = currentTime;
        }
        else
        {
            [self.client sendDataWithBytesArray2:dcBytes2
                                           Offset:index
                                            Count:ONE_DATA_LEN
                                 ToTargetHostName:[self.parameter getTargetHostname]
                                         WithPort:[self.parameter getTargetPort]
                                      andInterval:[self.parameter getIntervalDataCodeMillisecond]];
            index = (index + ONE_DATA_LEN) % [dcBytes2 count];
        }
        currentTime = [[NSDate date] timeIntervalSince1970];
        // check whether the udp is send enough time
        if ([[NSDate date] timeIntervalSince1970] - startTime > [self.parameter getWaitUdpSendingMillisecond]/1000.0)
        {
            break;
        }
    }
    
    return self.isSuc;
}

#pragma mark - 获取数据列表
-(NSArray *)getEsptouchResultList
{
    [self.esptouchResultArrayCondition lock];
    if ([self.esptouchResultArray count] == 0)
    {
        ESPTouchResult *esptouchResult = [[ESPTouchResult alloc]initWithIsSuc:NO andBssid:nil andInetAddrData:nil];
        esptouchResult.isCancelled = self.isCancelled;
        [self.esptouchResultArray addObject:esptouchResult];
    }
    [self.esptouchResultArrayCondition unlock];
    return self.esptouchResultArray;
}

#pragma mark - Esptouch结果是否成功
- (void) putEsptouchResultIsSuc: (BOOL)isSuc AndBssid: (NSString *)bssid AndInetAddr:(NSData *)inetAddr
{
    [self.esptouchResultArrayCondition lock];
    // check whether the result receive enough UDP response
    BOOL isTaskSucCountEnough = NO;
    NSNumber *countNumber = [self.bssidTaskSucCountDict objectForKey:bssid];
    int count = 0;
    if (countNumber != nil)
    {
        count = [countNumber intValue];
    }
    ++count;
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask __putEsptouchResult(): count = %d",count);
    }
    countNumber = [[NSNumber alloc]initWithInt:count];
    [self.bssidTaskSucCountDict setObject:countNumber forKey:bssid];
    isTaskSucCountEnough = count >= [self.parameter getThresholdSucBroadcastCount];
    if (!isTaskSucCountEnough)
    {
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask __putEsptouchResult(): count = %d, isn't enough", count);
        }
        [self.esptouchResultArrayCondition unlock];
        return;
    }
    // check whether the result is in the mEsptouchResultList already
    BOOL isExist = NO;
    for (id esptouchResultId in self.esptouchResultArray)
    {
        ESPTouchResult *esptouchResultInArray = esptouchResultId;
        if ([esptouchResultInArray.bssid isEqualToString:bssid])
        {
            isExist = YES;
            break;
        }
    }
    // only add the result who isn't in the mEsptouchResultList
    if (!isExist)
    {
        if (DEBUG_ON)
        {
            NSLog(@"ESPTouchTask __putEsptouchResult(): put one more result");
        }
        ESPTouchResult *esptouchResult = [[ESPTouchResult alloc]initWithIsSuc:isSuc andBssid:bssid andInetAddrData:inetAddr];
        [self.esptouchResultArray addObject:esptouchResult];
        if (self.esptouchDelegate != nil)
        {
            [self.esptouchDelegate onEsptouchResultAddedWithResult:esptouchResult];
        }
    }
    [self.esptouchResultArrayCondition unlock];
}

#pragma mark - 休息一下
// sleep some milliseconds
- (BOOL)sleep :(long) milliseconds
{
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask __sleep() start");
    }
    NSDate *date = [NSDate dateWithTimeIntervalSinceNow: milliseconds/1000.0];
    [self.condition lock];
    BOOL signaled = NO;
    while (!self.isWakeUp && (signaled = [self.condition waitUntilDate:date]))
    {
    }
    [self.condition unlock];
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask __sleep() end, receive signal is %@", signaled ? @"YES" : @"NO");
    }
    return signaled;
}

- (void)interrupt
{
    self.isInterrupt = YES;
    [self.client interrupt];
    [self.server interrupt];
    // notify the ESPTouchTask to wake up from sleep mode
    [self notify];
}

- (void) notify
{
    if (DEBUG_ON)
    {
        NSLog(@"ESPTouchTask __notify()");
    }
    [self.condition lock];
    self.isWakeUp = YES;
    [self.condition signal];
    [self.condition unlock];
}


#pragma mark - 结束
-(void)stopSmartConfig {
    [self interrupt];
    [self endBackgroundTask];
}

#pragma mark - 设置代理
//- (void) setEsptouchDelegate: (NSObject<ESPTouchDelegate> *) esptouchDelegate{
//    self.esptouchDelegate = esptouchDelegate;
//}
-(void) onEsptouchResultAddedWithResult: (ESPTouchResult *) result {
    NSLog(@"------------- 返回结果：%@",result);
//    self.smartConfigResultBlock(result);
}


#pragma mark - 初始化数据
-(void)initTouchTaskData {
    self.isSuc              = NO;
    self.isInterrupt        = NO;
    self.isWakeUp           = NO;
    self.isExecutedAlready  = NO;
    self.isSsidHidden       = YES;
}

#pragma mark - 懒加载
-(ESPTaskParameter *)parameter {
    if(!_parameter) {
        _parameter = [[ESPTaskParameter alloc]init];
    }
    return _parameter;
}

-(NSMutableArray *)esptouchResultArray {
    if(!_esptouchResultArray) {
        _esptouchResultArray = [NSMutableArray array];
    }
    return _esptouchResultArray;
}

-(NSMutableDictionary *)bssidTaskSucCountDict {
    if(!_bssidTaskSucCountDict) {
        _bssidTaskSucCountDict = [NSMutableDictionary dictionary];
    }
    return _bssidTaskSucCountDict;
}






@end
